# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
from deap import base, creator, tools, algorithms
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
import sys
from matplotlib import font_manager
from matplotlib.gridspec import GridSpec

# ==================== 字体配置 ====================
try:
    plt.rcParams['font.family'] = 'Arial'
    font_path = 'C:/Windows/Fonts/msyh.ttc'
    font_prop = font_manager.FontProperties(fname=font_path)
except Exception as e:
    print(f"字体加载警告: {str(e)}")

# ==================== 参数配置 ====================
class Config:
    DATA_PATH = r'C:\Users\kevin6366\Desktop\25mathorcup\question\B题\附件一：老城街区地块信息.xlsx'
    
    # 遗传算法参数
    NGEN = 150
    POP_SIZE = 30
    CXPB = 0.7
    MUTPB = 0.3
    TOURNSIZE = 3
    
    # 经济参数
    COMMUNICATION_COST = 3e4
    MAX_REPAIR = 20e4
    MONTHS_LOSS = 4
    DAYS_PER_MONTH = 30
    SOUTH_NORTH_RENT = 15
    EAST_WEST_RENT = 8
    WHOLE_YARD_RENT = 30
    BASE_BUDGET = 2000e4
    RESERVE_RATE = 0.3
    YEARS = 10

    def get_adj_bonus(self, adj_count):
        """动态毗邻加成系数 (每增加一对降低0.01)"""
        base = 1.2  # 初始系数
        decay = 0.01  # 每对衰减率
        return max(base - (adj_count-1)*decay, 1.0)  # 最低保持1.0

# ==================== 核心优化器 ==================== 
class RelocationOptimizer:
    def __init__(self, cfg):
        self.cfg = cfg
        self.hof = None
        self._load_data()
        self._precompute()
        print(f"可搬迁住户数: {len(self.migratable)}")

    def _load_data(self):
        """数据加载与预处理（保持原样）"""
        try:
            self.df = pd.read_excel(self.cfg.DATA_PATH).rename(columns=lambda x: x.strip())
            assert all(col in self.df.columns for col in ['地块ID','院落ID','地块方位','地块面积','是否有住户'])
        except Exception as e:
            print(f"数据加载失败: {str(e)}")
            sys.exit(1)

        orientation_map = {'南':1.0, '北':1.0, '东':0.7, '西':0.6}
        self.df['朝向得分'] = self.df['地块方位'].map(orientation_map).fillna(0)
        
        self.yard_df = self.df.groupby('院落ID').agg(
            院落面积=('地块面积', 'sum'),
            地块总数=('地块ID', 'count'),
            朝向种类=('地块方位', 'nunique')
        ).reset_index()

    def _precompute(self):
        """预计算邻接矩阵（保持原样）"""
        self.yard_ids = sorted(self.yard_df['院落ID'].unique())
        self.yard_id_to_idx = {yid:i for i,yid in enumerate(self.yard_ids)}
        self.idx_to_yard_id = {i:yid for i,yid in enumerate(self.yard_ids)}
        
        n = len(self.yard_ids)
        self.adj_matrix = np.zeros((n, n), dtype=int)
        for i in range(n-1):
            if self.yard_ids[i+1] - self.yard_ids[i] == 1:
                self.adj_matrix[i][i+1] = 1
                self.adj_matrix[i+1][i] = 1

        self.migratable = []
        self.candidate_map = {}
        residents = self.df[self.df['是否有住户'] == 1]
        for _, row in residents.iterrows():
            pid = row['地块ID']
            candidates = self.df[
                (self.df['是否有住户'] == 0) &
                (self.df['地块面积'] >= row['地块面积']) &
                (self.df['地块面积'] <= 1.3*row['地块面积']) &
                (self.df['朝向得分'] >= row['朝向得分'])
            ]
            if len(candidates) > 0:
                self.migratable.append(pid)
                self.candidate_map[pid] = candidates['地块ID'].tolist()
        
        self.plot_cache = {
            row['地块ID']: {
                '面积': row['地块面积'],
                '方位': row['地块方位'],
                '院落': row['院落ID']
            } for _, row in self.df.iterrows()
        }

    def _calculate_cost(self, migrated):
        """成本计算（保持原样）"""
        communication = len(migrated) * self.cfg.COMMUNICATION_COST
        area_loss = 0
        
        for pid, plot in migrated.items():
            if pid not in self.plot_cache or plot not in self.plot_cache:
                continue
            delta = self.plot_cache[plot]['面积'] - self.plot_cache[pid]['面积']
            rent_type = self.cfg.SOUTH_NORTH_RENT if self.plot_cache[plot]['方位'] in ['南','北'] else self.cfg.EAST_WEST_RENT
            area_loss += delta * rent_type * 365 * self.cfg.YEARS
        
        total_cost = communication + area_loss
        budget_limit = self.cfg.BASE_BUDGET * (1 + self.cfg.RESERVE_RATE)
        
        if total_cost > budget_limit:
            excess = total_cost - budget_limit
            total_cost += excess**2 * 1e-4
        return total_cost

    def evaluate(self, individual):
        """适应度评估（保持原样）"""
        selected = [self.migratable[i] for i, gene in enumerate(individual) if gene == 1]
        if not selected:
            return (0, 0, 0, 0, 1e9)
        
        migrated = {}
        used = set()
        for pid in selected:
            if pid not in self.candidate_map:
                continue
            for plot in self.candidate_map[pid]:
                if plot not in used and plot in self.plot_cache:
                    migrated[pid] = plot
                    used.add(plot)
                    break
        
        yard_residents = self.df[self.df['是否有住户']==1].groupby('院落ID')['地块ID'].apply(list).to_dict()
        emptied = [yid for yid, pids in yard_residents.items() if all(pid in migrated for pid in pids)]
        
        total_area = self.yard_df[self.yard_df['院落ID'].isin(emptied)]['院落面积'].sum()
        adj_count = 0
        valid_indices = [self.yard_id_to_idx[yid] for yid in emptied if yid in self.yard_id_to_idx]
        
        for i in range(len(valid_indices)):
            for j in range(i+1, len(valid_indices)):
                if self.adj_matrix[valid_indices[i]][valid_indices[j]] == 1:
                    adj_count += 1
        
        total_cost = self._calculate_cost(migrated)
        return (len(emptied), adj_count, total_area, -len(migrated), total_cost)

    def optimize(self):
        """优化主流程（保持原样）"""
        creator.create("FitnessMulti", base.Fitness, weights=(1.0, 0.5, 1.0, -0.2, -1.0))
        creator.create("Individual", list, fitness=creator.FitnessMulti)
        
        toolbox = base.Toolbox()
        toolbox.register("attr_bool", np.random.randint, 0, 2)
        toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, n=len(self.migratable))
        toolbox.register("population", tools.initRepeat, list, toolbox.individual)
        
        toolbox.register("evaluate", self.evaluate)
        toolbox.register("mate", tools.cxTwoPoint)
        toolbox.register("mutate", tools.mutFlipBit, indpb=0.2)
        toolbox.register("select", tools.selNSGA2)
        
        np.random.seed(42)
        pop = toolbox.population(n=self.cfg.POP_SIZE)
        self.hof = tools.ParetoFront()
        
        print("\n进化进度:")
        stats = tools.Statistics(lambda ind: ind.fitness.values)
        stats.register("avg", np.mean)
        stats.register("min", np.min)
        stats.register("max", np.max)
        
        pop, log = algorithms.eaMuPlusLambda(
            pop, toolbox,
            mu=self.cfg.POP_SIZE,
            lambda_=self.cfg.POP_SIZE,
            cxpb=self.cfg.CXPB,
            mutpb=self.cfg.MUTPB,
            ngen=self.cfg.NGEN,
            stats=stats,
            halloffame=self.hof,
            verbose=True
        )
        return self.hof

# ==================== 可视化模块 ====================
def scientific_visualization(optimizer, emptied_yards, migrated):
    sns.set_theme(style="whitegrid")
    colors = ["#4E79A7", "#F28E2B", "#E15759", "#76B7B2", "#59A14F"]
    
    fig = plt.figure(figsize=(18, 12))
    gs = GridSpec(2, 2, figure=fig)
    
    ax1 = fig.add_subplot(gs[0, 0], projection='3d')
    xs = [ind.fitness.values[0] for ind in optimizer.hof]
    ys = [ind.fitness.values[1] for ind in optimizer.hof]
    zs = [ind.fitness.values[2] for ind in optimizer.hof]
    ax1.scatter(xs, ys, zs, c=colors[0], s=50, alpha=0.8)
    ax1.set_xlabel('Vacated Yards', fontsize=10)
    ax1.set_ylabel('Adjacent Pairs', fontsize=10)
    ax1.set_zlabel('Total Area (m²)', fontsize=10)
    
    ax2 = fig.add_subplot(gs[0, 1])
    sns.heatmap(optimizer.adj_matrix, cmap="Blues", ax=ax2)
    ax2.set_title('Adjacency Matrix', fontsize=12)
    
    ax3 = fig.add_subplot(gs[1, 0])
    comm_cost = len(migrated) * optimizer.cfg.COMMUNICATION_COST
    area_comp = sum((optimizer.plot_cache[plot]['面积'] - optimizer.plot_cache[pid]['面积']) * 15 * 365 * 10 
                   for pid, plot in migrated.items())
    ax3.pie([comm_cost, area_comp], labels=['Communication', 'Area Comp'], 
            autopct='%1.1f%%', colors=colors[:2])
    
    ax4 = fig.add_subplot(gs[1, 1])
    years = np.arange(1, 11)
    total_area = optimizer.yard_df[optimizer.yard_df['院落ID'].isin(emptied_yards)]['院落面积'].sum()
    revenue = [total_area * optimizer.cfg.WHOLE_YARD_RENT * 365 * y for y in years]
    ax4.plot(years, np.array(revenue)/1e8, marker='o')
    ax4.set_xlabel('Years')
    ax4.set_ylabel('Revenue (100M yuan)')
    
    plt.tight_layout()
    plt.show()

# ==================== 结果解析 ====================
def analyze_result(hof, optimizer):
    budget_limit = optimizer.cfg.BASE_BUDGET * (1 + optimizer.cfg.RESERVE_RATE)
    best = next((ind for ind in hof if ind.fitness.values[4] <= budget_limit), None)
    
    if not best:
        print("未找到可行方案")
        return
    
    selected = [optimizer.migratable[i] for i, gene in enumerate(best) if gene == 1]
    migrated = {}
    for pid in selected:
        if pid in optimizer.candidate_map and len(optimizer.candidate_map[pid]) > 0:
            migrated[pid] = optimizer.candidate_map[pid][0]
    
    yard_residents = optimizer.df[optimizer.df['是否有住户']==1].groupby('院落ID')['地块ID'].apply(list).to_dict()
    emptied_yards = [yid for yid, pids in yard_residents.items() if all(pid in migrated for pid in pids)]
    
    total_area = optimizer.yard_df[optimizer.yard_df['院落ID'].isin(emptied_yards)]['院落面积'].sum()
    adj_count = sum(optimizer.adj_matrix[optimizer.yard_id_to_idx[i]][optimizer.yard_id_to_idx[j]] 
                   for i in emptied_yards for j in emptied_yards if i < j)
    total_cost = best.fitness.values[4]
    base_revenue = total_area * optimizer.cfg.WHOLE_YARD_RENT * 365 * optimizer.cfg.YEARS
    
    # 动态系数计算
    dynamic_bonus = optimizer.cfg.get_adj_bonus(adj_count)
    final_revenue = base_revenue * (dynamic_bonus ** adj_count)
    profit = final_revenue - total_cost
    
    print("\n" + "="*80)
    print("最终搬迁方案：")
    print("="*80)
    print(f"腾空完整院落数量: {len(emptied_yards)}个")
    print(f"相邻院落对数: {adj_count}对")
    print(f"腾空总面积: {total_area:.2f}平方米")
    print(f"搬迁户数: {len(migrated)}户")
    print("\n搬迁明细（前10条）：")
    print("-"*80)
    for pid, plot in list(migrated.items())[:10]:
        print(f"住户 {pid}（原属院落 {optimizer.plot_cache[pid]['院落']}）→ 迁入地块 {plot}（目标院落 {optimizer.plot_cache[plot]['院落']}）")
    
    print("\n经济核算：")
    print("-"*80)
    print(f"总成本: {total_cost/1e4:.2f}万元")
    print(f"基础收益: {base_revenue/1e8:.2f}亿元") 
    print(f"动态加成系数: {dynamic_bonus:.3f}（每对）")
    print(f"总毗邻加成: {((dynamic_bonus ** adj_count)-1)*100:.1f}%")
    print(f"最终收益: {final_revenue/1e8:.2f}亿元")
    print(f"净盈利: {profit/1e8:.2f}亿元")
    print("="*80)
    
    scientific_visualization(optimizer, emptied_yards, migrated)

# ==================== 主程序 ====================
if __name__ == "__main__":
    cfg = Config()
    optimizer = RelocationOptimizer(cfg)
    hof = optimizer.optimize()
    analyze_result(hof, optimizer)